package user.list;


public class SynchronizedBuffer<T> {

	private Buffer<T> a = new Buffer<T>();
	private Buffer<T> b = new Buffer<T>();
	private Object readsync = new Object();
	private Object writesync = new Object();
	private int writeBufferSize = 0;
	private int maxWriteBufferSize = 2;
	private boolean state = false;

	public SynchronizedBuffer() {}

	public void interruptablePush(T t) throws InterruptedException {
		synchronized (this.writesync) {
			Buffer<T> bufferToWrite;
			while (true) {
				if (this.writeBufferSize < this.maxWriteBufferSize) {
					this.writeBufferSize++;
					bufferToWrite = state ? a : b;
					break;
				} else this.writesync.wait();
			}
			bufferToWrite.push(t);
			this.writesync.notifyAll();
		}
	}

	public void push(T t) {
		synchronized (this.writesync) {
			Buffer<T> bufferToWrite;
			while (true) {
				if (this.writeBufferSize < this.maxWriteBufferSize) {
					this.writeBufferSize++;
					bufferToWrite = state ? a : b;
					break;
				} else this.uninterruptableWait(this.writesync);
			}
			bufferToWrite.push(t);
			this.writesync.notifyAll();
		}
	}

	public T interruptablePop() throws InterruptedException {
		T toReturn;
		synchronized (this.readsync) {
			Buffer<T> bufferToRead = state ? b : a;
			toReturn = bufferToRead.pop();
			if (toReturn != null) return toReturn;
			synchronized (this.writesync) {
				this.state = !this.state;
				this.writeBufferSize = 0;
				if ((this.state ? b : a).peek() == null) this.writesync.wait();
				else this.writesync.notifyAll();
			}
			return this.interruptablePop();
		}
	}

	private void uninterruptableWait(Object waitObject) {
		synchronized (waitObject) {
			boolean interrupted = false;
			while (true) {
				try {
					waitObject.wait();
					break;
				} catch (InterruptedException e) {
					interrupted = true;
				}
			}
			if (interrupted) Thread.currentThread().interrupt();
		}
	}

	public T pop() {
		T toReturn;
		synchronized (this.readsync) {
			Buffer<T> bufferToRead = state ? b : a;
			toReturn = bufferToRead.pop();
			if (toReturn != null) return toReturn;
			synchronized (this.writesync) {
				this.state = !this.state;
				this.writeBufferSize = 0;
				if ((this.state ? b : a).peek() == null) this.uninterruptableWait(this.writesync);
				else {
					this.writesync.notifyAll();
				}
			}
			return this.pop();
		}
	}

}
